/**
  ******************************************************************************
  * @file    flash.c
  * @author  Puya Application Team
  * @brief   Contains FLASH HW configuration
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 Puya Semiconductor.
  * All rights reserved.</center></h2>
  *
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "flash.h"
#include "usart.h"
#include "iap.h"
#include "wdg.h"

/**
 * @brief  Write data to FLASH memory.
 * @param  Target address
 * @param  Data buffer
 * @param  Data length
 * @retval SUCCESS or ERROR
 */
ErrorStatus WriteFlash(uint32_t uiAddr, uint8_t* pubBuf, uint8_t ubLength)
{
  uint16_t i = 0;
  uint16_t j = 0;
  uint16_t uwLength = 0;
  uint32_t uiOffsetAddr = 0;
  ErrorStatus enFlag = SUCCESS;

  uwLength = ubLength + 1;

  if ((uwLength & 0x7F) || (uiAddr & 0x7F))
  {
    return ERROR;
  }

  SET_BIT(FLASH->CR, (FLASH_CR_PG | FLASH_CR_EOPIE));

  uiOffsetAddr = 0;

  for (i = 0; i < uwLength / 0x80; i++)
  {
    for (j = 0; j < 0x20; j++)
    {
      M32(uiAddr + uiOffsetAddr) = M32(pubBuf + uiOffsetAddr);

      uiOffsetAddr += 4;

      if ((0x20 - 2) == j)
      {
        SET_BIT(FLASH->CR, FLASH_CR_PGSTRT);
      }
    }

    while (FLASH_SR_BSY == READ_BIT(FLASH->SR, FLASH_SR_BSY))
    {
      WdgRefresh();
    }

    if (FLASH_SR_EOP == READ_BIT(FLASH->SR, FLASH_SR_EOP))
    {
      enFlag = SUCCESS;
      //clear EOP flag
      SET_BIT(FLASH->SR, FLASH_SR_EOP);
    }
    else
    {
      enFlag = ERROR;
      break;
    }
  }

  CLEAR_BIT(FLASH->CR, (FLASH_CR_EOPIE | FLASH_CR_PG));

  return enFlag;
}

/**
 * @brief  Write data to Option bytes.
 * @param  uiAddr   Target address
 * @param  pubBuf   Data buffer
 * @param  ubLength Data length
 * @retval SUCCESS or ERROR
 */
ErrorStatus WriteOption(uint32_t uiAddr, uint8_t* pubBuf, uint8_t ubLength)
{
  uint32_t uiRegOPTR   = FLASH->OPTR;
  uint32_t uiRegWRPR   = FLASH->WRPR;
  uint32_t uiRegPCROPR = FLASH->PCROPR;
  uint32_t uiRegSECR   = FLASH->SECR;

  if (READ_BIT(FLASH->CR, FLASH_CR_OPTLOCK) != 0x00U)
  {
    WRITE_REG(FLASH->OPTKEYR, FLASH_OPTKEY1);
    WRITE_REG(FLASH->OPTKEYR, FLASH_OPTKEY2);
  }

  for (uint16_t i = 0; i < ubLength + 1; i++)
  {
    if ((OPTR1_BASE + 0) == (uiAddr + i))
    {
      uiRegOPTR &= ~(0x000000FF);
      uiRegOPTR |= (pubBuf[i] << 0);
    }
    else if ((OPTR1_BASE + 1) == (uiAddr + i))
    {
      uiRegOPTR &= ~(0x0000FF00);
      uiRegOPTR |= (pubBuf[i] << 8);
    }
    else if ((OPTR2_BASE + 0) == (uiAddr + i))
    {
      uiRegOPTR &= ~(0x00FF0000);
      uiRegOPTR |= (pubBuf[i] << 16);
    }
    else if ((OPTR2_BASE + 1) == (uiAddr + i))
    {
      uiRegOPTR &= ~(0xFF000000);
      uiRegOPTR |= (pubBuf[i] << 24);
    }

    if ((WRPR_BASE + 0) == (uiAddr + i))
    {
      uiRegWRPR &= ~(0x000000FF);
      uiRegWRPR |= (pubBuf[i] << 0);
    }
    else if ((WRPR_BASE + 1) == (uiAddr + i))
    {
      uiRegWRPR &= ~(0x0000FF00);
      uiRegWRPR |= (pubBuf[i] << 8);
    }

    if ((PCROP0SR_BASE + 0) == (uiAddr + i))
    {
      uiRegPCROPR &= ~(0x000000FF);
      uiRegPCROPR |= (pubBuf[i] << 0);
    }
    else if ((PCROP0SR_BASE + 1) == (uiAddr + i))
    {
      uiRegPCROPR &= ~(0x0000FF00);
      uiRegPCROPR |= (pubBuf[i] << 8);
    }
    else if ((PCROP0ER_BASE + 0) == (uiAddr + i))
    {
      uiRegPCROPR &= ~(0x00FF0000);
      uiRegPCROPR |= (pubBuf[i] << 16);
    }
    else if ((PCROP0ER_BASE + 1) == (uiAddr + i))
    {
      uiRegPCROPR &= ~(0xFF000000);
      uiRegPCROPR |= (pubBuf[i] << 24);
    }

    if ((SECR_BASE + 0) == (uiAddr + i))
    {
      uiRegSECR &= ~(0x000000FF);
      uiRegSECR |= (pubBuf[i] << 0);
    }
    else if ((SECR_BASE + 1) == (uiAddr + i))
    {
      uiRegSECR &= ~(0x0000FF00);
      uiRegSECR |= (pubBuf[i] << 8);
    }
  }

  FLASH->OPTR   = uiRegOPTR;
  FLASH->WRPR   = uiRegWRPR;
  FLASH->PCROPR = uiRegPCROPR;
  FLASH->SECR   = uiRegSECR;

  SET_BIT(FLASH->CR, (FLASH_CR_EOPIE | FLASH_CR_OPTSTRT));
  M32(0x1FFF0F80) = 0xFF;

  while (FLASH_SR_BSY == READ_BIT(FLASH->SR, FLASH_SR_BSY))
  {
    WdgRefresh();
  }

  ErrorStatus enFlag = (FLASH_SR_EOP == READ_BIT(FLASH->SR, FLASH_SR_EOP)) ? SUCCESS : ERROR;

  SET_BIT(FLASH->SR, FLASH_SR_EOP);

  CLEAR_BIT(FLASH->CR, (FLASH_CR_EOPIE | FLASH_CR_OPTSTRT));

  if (SUCCESS == enFlag)
  {
    UsartSendByte(ACK_BYTE);

    SET_BIT(FLASH->CR, FLASH_CR_OBL_LAUNCH);
  }

  return enFlag;
}

/**
 * @brief  Perform FLASH mass erase.
 * @retval SUCCESS or ERROR
 */
ErrorStatus MassErase(void)
{
  /* Set the MER bit in the FLASH_CR register */
  SET_BIT(FLASH->CR, (FLASH_CR_MER));

  /* Trigger */
  M32(FLASH_BASE) = 0xFFFFFFFFUL;

  /* Wait for the BSY bit to be cleared */
  while (FLASH_SR_BSY == READ_BIT(FLASH->SR, FLASH_SR_BSY))
  {
    WdgRefresh();
  }

  /* Check if the EOP flag is set */
  ErrorStatus enFlag = (FLASH_SR_EOP == READ_BIT(FLASH->SR, FLASH_SR_EOP)) ? \
                       SUCCESS : ERROR;

  /* Clear EOP flag */
  SET_BIT(FLASH->SR, FLASH_SR_EOP);

  CLEAR_BIT(FLASH->CR, (FLASH_CR_MER));

  return enFlag;
}

/**
 * @brief  Erase specified FLASH pages.
 * @param  Pointer to erase options buffer
 * @param  Buffer size
 * @retval SUCCESS or ERROR
 */
ErrorStatus PageErase(uint16_t* puwBuf, uint8_t ubLength, uint8_t ubPageCount)
{
  uint32_t uiAddr = 0U;
  ErrorStatus enFlag = SUCCESS;

  SET_BIT(FLASH->CR, (FLASH_CR_PER));

  for (uint8_t i = 0; i < ubLength + 1; i++)
  {
    if (SUCCESS != enFlag)
    {
      break;
    }

    for (uint8_t j = 0; j < ubPageCount; j++)
    {
      WdgRefresh();

      uiAddr = FLASH_BASE + (puwBuf[i] * ubPageCount + j) * 0x80;

      M32(uiAddr) = 0xFFFFFFFF;

      while (FLASH_SR_BSY == READ_BIT(FLASH->SR, FLASH_SR_BSY))
      {
        WdgRefresh();
      }

      if (FLASH_SR_EOP == READ_BIT(FLASH->SR, FLASH_SR_EOP))
      {
        enFlag = SUCCESS;

        SET_BIT(FLASH->SR, FLASH_SR_EOP);
      }
      else
      {
        enFlag = ERROR;
        break;
      }
    }
  }

  CLEAR_BIT(FLASH->CR, (FLASH_CR_PER));

  return enFlag;
}

/**
 * @brief  Erase specified FLASH sectors.
 * @param  puwBuf     Pointer to erase options buffer
 * @param  ubLength  Buffer size
 * @retval SUCCESS or ERROR
 */
ErrorStatus SectorErase(uint16_t* puwBuf, uint8_t ubLength)
{
  ErrorStatus enFlag = SUCCESS;

  SET_BIT(FLASH->CR, (FLASH_CR_SER));

  for (uint8_t i = 0; i < ubLength + 1; i++)
  {
    WdgRefresh();

    M32(FLASH_BASE + puwBuf[i] * 0x1000) = 0xFFFFFFFFUL;

    while (FLASH_SR_BSY == READ_BIT(FLASH->SR, FLASH_SR_BSY))
    {
      WdgRefresh();
    }

    if (FLASH_SR_EOP == READ_BIT(FLASH->SR, FLASH_SR_EOP))
    {
      enFlag = SUCCESS;
      SET_BIT(FLASH->SR, FLASH_SR_EOP);
    }
    else
    {
      enFlag = ERROR;
      break;
    }
  }

  CLEAR_BIT(FLASH->CR, (FLASH_CR_SER));

  return enFlag;
}
